﻿using System;
using System.Collections.Generic;

namespace LaoLuo.Time
{

    /// <summary>
    /// 提供一个周为基本单位的时间片段的相关操作
    /// </summary>
    public struct WeeklyTimePeriod
    {
        /// <summary>
        /// 时间片段的起始时间
        /// </summary>
        private readonly WeeklyTimePoint start;

        /// <summary>
        /// 时间片段的结束时间
        /// </summary>
        private readonly WeeklyTimePoint end;

        /// <summary>
        /// 初始化一个WeeklyTimePeriod
        /// </summary>
        /// <param name="startPoint">时间片段的起始时间</param>
        /// <param name="endPoint">时间片段的结束时间</param>
        public WeeklyTimePeriod(WeeklyTimePoint startPoint, WeeklyTimePoint endPoint)
        {
            this.start = startPoint;
            this.end = endPoint;

            if (this.start > this.end)
            {
                this.end.Weight += WeeklyTimePoint.ModWeight; // todo
            }
        }

        /// <summary>
        /// 获取时间片段的起始点
        /// </summary>
        public WeeklyTimePoint StartPoint
        {
            get { return start; }
        }

        /// <summary>
        /// 获取时间片段的结束点
        /// </summary>
        public WeeklyTimePoint EndPoint
        {
            get { return end; }
        }

        public WeeklyTimePeriod GetReversed()
        {
            return new WeeklyTimePeriod(this.EndPoint, this.StartPoint);
        }

        /// <summary>
        /// 获取指定时间点是否在当前时间片段内
        /// </summary>
        /// <param name="point">时间点</param>
        /// <returns>时间点是否在时间片段内</returns>
        public bool HasPoint(WeeklyTimePoint point)
        {
            return EndPointIsAfter(point) && StartPointIsBefore(point);
        }

        /// <summary>
        /// 获取指定时间点是否在当前时间片段的终结点前
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public bool EndPointIsAfter(WeeklyTimePoint point)
        {
            if (this.end.IsClosed)
            {
                return point <= this.end;
            }

            return point < this.end;
        }

        /// <summary>
        /// 获取指定时间点是否在当前时间片段的起始点后
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public bool StartPointIsBefore(WeeklyTimePoint point)
        {
            if (this.start.IsClosed)
            {
                return this.start <= point;
            }

            return this.start < point;
        }

        public string ToString(string format)
        {
            return string.Format("{0} -- {1}", this.StartPoint.ToString(format), this.EndPoint.ToString(format));
        }

        /// <summary>
        /// WeeklyTimePeriod使用的时间点定义,可表示如"周一的09:02:01"
        /// </summary>
        public struct WeeklyTimePoint
        {
            /// <summary>
            /// 时间点的权值
            /// </summary>
            private int weight;

            /// <summary>
            /// 小时,分钟,秒部分的存放数组
            /// </summary>
            private readonly int[] timePart;

            /// <summary>
            /// 周的第几天
            /// </summary>
            private readonly DayOfWeek dayOfWeek;

            /// <summary>
            /// 时间点在区间中是否闭合
            /// </summary>
            private readonly bool isClosed;

            /// <summary>
            /// 最大值
            /// </summary>
            public static readonly WeeklyTimePoint MaxValue = new WeeklyTimePoint(DayOfWeek.Saturday, 23, 59, 59);

            /// <summary>
            /// 最小值
            /// </summary>
            public static readonly WeeklyTimePoint MinValue = new WeeklyTimePoint(DayOfWeek.Sunday, 0, 0, 0);

            /// <summary>
            /// 初始化一个时间点
            /// </summary>
            /// <param name="dayOfWeek">周的第几天</param>
            /// <param name="hour">小时部分</param>
            /// <param name="minute">分钟部分</param>
            /// <param name="second">秒部分</param>
            /// <param name="isClosed">时间点在区间中是否闭合</param>
            public WeeklyTimePoint(DayOfWeek dayOfWeek, int hour = 0, int minute = 0, int second = 0, bool isClosed = false)
            {
                minute += second / 60;
                second = second % 60;

                hour += minute / 60;
                minute = minute % 60;

                dayOfWeek = EnumInternals<DayOfWeek>.CircularList[(int)dayOfWeek + hour / 24];
                hour = hour % 24;

                this.timePart = new int[3] { hour, minute, second };
                this.dayOfWeek = dayOfWeek;
                this.weight = second + minute * 60 + hour * 60 * 60 + (int)dayOfWeek * 60 * 60 * 24;
                this.isClosed = isClosed;
            }

            /// <summary>
            /// 初始化一个时间点
            /// </summary>
            /// <param name="dayOfWeek">周的第几天</param>
            /// <param name="hour">小时部分</param>
            /// <param name="minute">分钟部分</param>
            /// <param name="second">秒部分</param>
            /// <param name="isClosed">时间点在区间中是否闭合</param>
            public WeeklyTimePoint(int dayOfWeek, int hour = 0, int minute = 0, int second = 0, bool isClosed = false)
                : this(EnumHelper.TryParse<DayOfWeek>(dayOfWeek).Value, hour, minute, second, isClosed)
            {

            }

            /// <summary>
            /// (最大权重+1)
            /// </summary>
            public static int ModWeight
            {
                get
                {
                    return MaxValue.Weight + 1;
                }
            }

            /// <summary>
            /// 返回一个新的WeeklyTimePoint, 它的值为当前实例的值添加指定的时分秒
            /// </summary>
            /// <param name="hour">要添加到当前实例的小时数</param>
            /// <param name="minute">要添加到当前实例的分钟数</param>
            /// <param name="second">要添加到当前实例的秒数</param>
            /// <returns>一个新的WeeklyTimePoint</returns>
            public WeeklyTimePoint Add(int hour, int minute = 0, int second = 0)
            {
                return new WeeklyTimePoint(this.dayOfWeek, this.Hour + hour, this.Minute + minute, this.Second + second);
            }

            /// <summary>
            /// 获取时间点是周的第几天
            /// </summary>
            public DayOfWeek DayOfWeek
            {
                get { return dayOfWeek; }
            }

            /// <summary>
            /// 获取时间点在区间中是否闭合
            /// </summary>
            public bool IsClosed
            {
                get
                {
                    return this.isClosed;
                }
            }

            /// <summary>
            /// 设置或获取时间点的权值
            /// </summary>
            public int Weight
            {
                get { return this.weight; }
                internal set { this.weight = value; }
            }

            /// <summary>
            /// 获取时间点的小时部分
            /// </summary>
            public int Hour
            {
                get
                {
                    return this.timePart[0];
                }
            }

            /// <summary>
            /// 获取时间点的分钟部分
            /// </summary>
            public int Minute
            {
                get
                {
                    return this.timePart[1];
                }
            }

            /// <summary>
            /// 获取时间点的秒部分
            /// </summary>
            public int Second
            {
                get
                {
                    return this.timePart[2];
                }
            }

            #region operator

            /// <summary>
            /// 重载==
            /// </summary>
            /// <param name="p1">p1</param>
            /// <param name="p2">p2</param>
            /// <returns>权重是否==</returns>
            public static bool operator ==(WeeklyTimePoint p1, WeeklyTimePoint p2)
            {
                return p1.Equals(p2);
            }

            /// <summary>
            /// 重载!=
            /// </summary>
            /// <param name="p1">p1</param>
            /// <param name="p2">p2</param>
            /// <returns>权重是否!=</returns>
            public static bool operator !=(WeeklyTimePoint p1, WeeklyTimePoint p2)
            {
                return !p1.Equals(p2);
            }

            /// <summary>
            /// 重载&lt;
            /// </summary>
            /// <param name="p1">p1</param>
            /// <param name="p2">p2</param>
            /// <returns>权重是否&lt;</returns>
            public static bool operator <(WeeklyTimePoint p1, WeeklyTimePoint p2)
            {
                return p1.weight < p2.weight;
            }

            /// <summary>
            /// 重载>
            /// </summary>
            /// <param name="p1">p1</param>
            /// <param name="p2">p2</param>
            /// <returns>权重是否></returns>
            public static bool operator >(WeeklyTimePoint p1, WeeklyTimePoint p2)
            {
                return p1.weight > p2.weight;
            }

            /// <summary>
            /// 重载&lt;=
            /// </summary>
            /// <param name="p1">p1</param>
            /// <param name="p2">p2</param>
            /// <returns>权重是否&lt;=</returns>
            public static bool operator <=(WeeklyTimePoint p1, WeeklyTimePoint p2)
            {
                return p1.weight <= p2.weight;
            }

            /// <summary>
            /// 重载>=
            /// </summary>
            /// <param name="p1">p1</param>
            /// <param name="p2">p2</param>
            /// <returns>权重是否>=</returns>
            public static bool operator >=(WeeklyTimePoint p1, WeeklyTimePoint p2)
            {
                return p1.weight >= p2.weight;
            }

            #endregion

            /// <summary>
            /// format形如"{0}的{1}"则返回"周一的22:22:22"
            /// </summary>
            /// <param name="format"></param>
            /// <returns></returns>
            public string ToString(string format)
            {
                return string.Format(format, this.dayOfWeek.ToChinese(), string.Format("{0}:{1}:{2}", this.Hour, this.Minute, this.Second));
            }

            /// <summary>
            /// 获取最近的DayOfWeek及时分秒为实例值的DateTime
            /// </summary>
            /// <param name="basePoint"></param>
            /// <returns></returns>
            public DateTime GetDateTime(DateTime? basePoint = null)
            {
                return DateTimeHelper.GetLatestDate(this.dayOfWeek, basePoint).ChangeTime(this.Hour, this.Minute, this.Second);
            }

            public override bool Equals(object obj)
            {
                if (obj == null || GetType() != obj.GetType())
                    return false;

                return this.Weight == ((WeeklyTimePoint)obj).Weight;
            }

            public override int GetHashCode()
            {
                return this.GetHashCode(this.weight);
            }
        }

        public override bool Equals(object obj)
        {
            if (obj == null || GetType() != obj.GetType())
                return false;

            var o = (WeeklyTimePeriod)obj;

            return this.StartPoint == o.StartPoint && this.EndPoint == o.EndPoint;
        }

        public override int GetHashCode()
        {
            return this.GetHashCode(StartPoint, EndPoint);
        }

        public static bool operator ==(WeeklyTimePeriod x, WeeklyTimePeriod y)
        {
            return x.Equals(y);
        }

        public static bool operator !=(WeeklyTimePeriod x, WeeklyTimePeriod y)
        {
            return !x.Equals(y);
        }
    }
}
